package com.tensai.study.java.basic.basic_operation;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

/**
 * 算术异常
 *
 * @author Tensai
 */
@Slf4j
public class ArithmeticExceptionTest {

	@Test
	public void test1() {
		int s;
		s = addExact(4, -5);
		s = addExact(4, 5);
		s = addExact(-4, -5);
		s = addExact(-4, 5);
		s = addExact(5, Integer.MAX_VALUE);
	}

	/**
	 * 判断溢出
	 * <br /> 就是判断结果的最高比特位和两个数的亦或 与之后是否是1
	 * <br /> 反推&之后为1的情况 两个全是1
	 * <br /> 亦或之后能都是1的说明 两个数和结果符号不同
	 * <br /> 现实中肯定不会出现这种情况嘛，所以一定是计算的时候溢出了
	 * <li>x>0, y>0, r>0 </li>
	 *
	 * @see java.lang.Math#addExact(int, int)
	 *
	 * @param x 被加数
	 * @param y 加数
	 * @return 和
	 */
	private int addExact(int x, int y) {
		// 两个数加和的结果
		int r = x + y;
		int a = x ^ r;
		int b = y ^ r;
		int c = ((x ^ r) & (y ^ r));
		log.info("{} + {} r={} a={} b={} c={}", x, y, r, a, b, c);
		log.info("x={} y={} r={} a={} b={} c={} {}", toBin(x), toBin(y), toBin(r), toBin(a), toBin(b), toBin(c), c);
		// 这个地方判断 (x ^ r) 和 (y ^ r) 的最高比特位是否都是1 也就是两个异或的结果都是负数 注：最高位是符号位
		// 如果都是1 与运算之后才会满足条件 判定为溢出
		// 为什么要这么写？
		// 举个例子 5 + 2147483647（Integer.Max_VALUE) 应该是 2147483652
		// 但是 Int只有三十二位
		// 0000 0000 0000 0000 0000 0000 0000 0101  =  5
		// 0111 1111 1111 1111 1111 1111 1111 1111  =  2147483647（Integer.Max_VALUE)
		// 1000 0000 0000 0000 0000 0000 0000 0100  = 5 + 2147483647（Integer.Max_VALUE) = -2147483644
		// 但是最高位是符号位 为1 表示为负数 用补码解析 所以得到的溢出结果 为 -2147483644
		// 计算机是不知道溢出的 这个可以回想一下 电子电路的加法器
		// 然后用这个结果 分别异或两个加和的数  异或这个运算主要是判断 结果和每个加数是否同号，并把结果存到最高位上
		// 如果结果的最高位为1 说明两者异号 也就是一正一负
		// 反之 两者同号
		// 所以最高明的地方来了
		// 这个if的条件判别式 解释出来就是 如果加和结果和两个加数都不同符号 证明就是溢出了
		// 再白话一点就是 两个正数不可能加和为负数 两个负数的加和也不可能是正数
		// 有同学要问了 如果一个负数和一个正数的加和也溢出了 真么判断也可以吗？
		// 我想说 同学 你再好好想想？
		if (((x ^ r) & (y ^ r)) < 0) {
			throw new ArithmeticException("integer overflow");
		}
		return r;
	}

	private String toBin(Integer i) {
		return Integer.toBinaryString(i);
	}
}
